package main

import (
	"fmt"
	"log"
	"math"
)

/*
 * 大地坐标系资料WGS-84 长半径a=6378137 短半径b=6356752.3142 扁率f=1/298.2572236
 */
var (
	//长半径a=6378137
	a = 6378137
	//短半径b=6356752.3142
	b = 6356752.3142
	//扁率f=1/298.2572236
	f = 1 / 298.2572236
)

func main() {
	pathLists := []Point{
		{X: -3.68, Y: 6.65},
		{X: 4.1, Y: 6.35},
		{X: -6.78, Y: 2.65},
	}
	region, _ := IsPtInPoly(&Point{X: -6.14, Y: 4.59}, pathLists, false)
	log.Println("多边形验证：", region)
	//circle := isInRangeCircle(1000, 116.407916, 39.914133, 116.402616, 39.915488)
	//log.Println("圆形验证：", circle)
	//for i := 0; i <= 360; i += 10 {
	//	lat, f2 := computerThatLonLat(121.481912, 31.238591, float64(i), 1000)
	//	fmt.Println(lat, ",", f2)
	//}
}

/**
 * 计算另一点经纬度
 * @param lon  经度
 * @param lat  维度
 * @param azimuth 方位角(0-360)
 * @param dist 距离（米）
 */
func computerThatLonLat(lon, lat, azimuth, dist float64) (float64, float64) {
	alpha1 := rad(azimuth)
	sinAlpha1 := math.Sin(alpha1)
	cosAlpha1 := math.Cos(alpha1)
	tanU1 := (1 - f) * math.Tan(rad(lat))
	cosU1 := 1 / math.Sqrt(1+tanU1*tanU1)
	sinU1 := tanU1 * cosU1
	sigma1 := math.Atan2(tanU1, cosAlpha1)
	sinAlpha := cosU1 * sinAlpha1
	cosSqAlpha := 1 - sinAlpha*sinAlpha
	uSq := cosSqAlpha * (float64(a*a) - b*b) / (b * b)
	A := 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
	B := uSq / 1024 * (256 + uSq*(-128+uSq*(74-47*uSq)))

	var cos2SigmaM, sinSigma, cosSigma float64
	sigma := dist / (b * A)
	sigmaP := 2 * math.Pi
	for math.Abs(sigma-sigmaP) > 1e-12 {
		cos2SigmaM = math.Cos(2*sigma1 + sigma)
		sinSigma = math.Sin(sigma)
		cosSigma = math.Cos(sigma)
		deltaSigma := B * sinSigma * (cos2SigmaM + B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)))
		sigmaP = sigma
		sigma = dist/(b*A) + deltaSigma
	}

	tmp := sinU1*sinSigma - cosU1*cosSigma*cosAlpha1
	lat2 := math.Atan2(sinU1*cosSigma+cosU1*sinSigma*cosAlpha1,
		(1-f)*math.Sqrt(sinAlpha*sinAlpha+tmp*tmp))
	lambda := math.Atan2(sinSigma*sinAlpha1, cosU1*cosSigma-sinU1*sinSigma*cosAlpha1)
	C := f / 16 * cosSqAlpha * (4 + f*(4-3*cosSqAlpha))
	L := lambda - (1-C)*f*sinAlpha*(sigma+C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)))

	//revAz := math.Atan2(sinAlpha, -tmp) // final bearing
	//fmt.Println(revAz);
	//fmt.Println(lon+deg(L), ",", deg(lat2))
	return lon + deg(L), deg(lat2)
}

/**
 * 度换成弧度
 * @param d 度
 * @return 弧度
 */
func rad(d float64) float64 {
	return d * math.Pi / 180.0
}

/**
 * 弧度换成度
 * @param x 弧度
 * @return 度
 */
func deg(x float64) float64 {
	return x * 180 / math.Pi
}

type PathList struct {
	Longitude float64
	Latitude  float64
}

/**
* 判断是否在指定范围内
* @param latitude  点的纬度
* @param longitude 点的经度
* @param pathList  范围点坐标集合
* @return bool
 */
func isInRegion(longitude, latitude float64, pathList []PathList) bool {
	//点小于3无法构成多边形
	if len(pathList) < 3 {
		return false
	}
	var (
		ESP                                         = 1e-9 // 无限小的正数
		longStart, latStart, longEnd, latEnd, dLong float64
		iSum                                        int
	)
	pathCount := len(pathList)
	for i := 0; i < pathCount; i++ {
		nextIndex := i + 1
		if i == pathCount-1 {
			nextIndex = 0
		}
		longStart = pathList[i].Longitude
		latStart = pathList[i].Latitude
		longEnd = pathList[nextIndex].Longitude
		latEnd = pathList[nextIndex].Latitude
		//目标点是否在目标区域边上
		if (math.Abs((longStart-longitude)*(latEnd-latitude)-(longEnd-longitude)*(latStart-latitude)) < ESP) && ((longitude-longStart)*(longitude-longEnd) <= 0) && (latitude-latStart)*(latitude-latEnd) <= 0 {
			return true
		}
		//判断纬度即Y坐标是否在2点的Y坐标内，只有在其内水平线才会相交
		if (latitude >= latStart && latitude < latEnd) || (latitude >= latEnd && latitude < latStart) {
			if math.Abs(latStart-latEnd) > 0 {
				dLong = longStart - ((longStart-longEnd)*(latStart-latitude))/(latStart-latEnd)
				if dLong < longitude {
					iSum++
				}
			}
		}
	}
	if (iSum % 2) != 0 {
		return true
	}
	return false
}

/**
 * 判断是否在范围内
 * @param radius    圆的半径
 * @param latitude  点的纬度
 * @param longitude 点的经度
 * @param latitudeCircle      圆的纬度(圆心)
 * @param longitudeCircle      圆的经度(圆心)
 * @return bool
 */
func isInRangeCircle(radius, longitude, latitude, longitudeCircle, latitudeCircle float64) bool {
	R := 6378137.0
	dLat := (latitudeCircle - latitude) * math.Pi / 180
	dLng := (longitudeCircle - longitude) * math.Pi / 180
	a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Cos(latitude*math.Pi/180)*math.Cos(latitudeCircle*math.Pi/180)*math.Sin(dLng/2)*math.Sin(dLng/2)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
	d := R * c
	dis := math.Round(d)
	if dis <= radius { //点在圆内
		return true
	} else {
		return false
	}
}

type operationPoint struct {
	Across   float64
	Vertical float64
}

// IsPtInPoly .
// 判断点是否在多边形内，如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
// @param point 检测点
// @param pts   多边形的顶点
// @return      点在多边形内返回true,否则返回false.
func IsPtInPoly(point *Point, pts []Point, isOutDoorCoordinates bool) (bool, error) {
	var (
		N              int
		intersectCount int
		precision      float64
		p, p1, p2      operationPoint
	)
	if nil == point || 0 == len(pts) || len(pts) < 3 {
		return false, fmt.Errorf("参数不能为空，且多边形顶点数不能小于3")
	}
	N = len(pts)
	// 交叉点计数
	intersectCount = 0
	// 浮点类型计算时候与0比较时候的容差
	precision = 2e-10
	// 左顶点
	p = operationPoint{Across: point.X, Vertical: point.Y}
	p1 = operationPoint{pts[0].X, pts[0].Y}
	if isOutDoorCoordinates {
		p = operationPoint{Across: point.Longitude, Vertical: point.Latitude}
		p1 = operationPoint{pts[0].Longitude, pts[0].Latitude}
	}
	// 检查所有射线
	for i := 1; i <= N; i++ {
		// 右顶点
		p2 = operationPoint{Across: pts[i%N].X, Vertical: pts[i%N].Y}
		if isOutDoorCoordinates {
			p2 = operationPoint{pts[i%N].Longitude, pts[i%N].Latitude}
		}
		if p == p1 || p == p2 {
			// 如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
			return true, nil
		}
		// 射线超出相交范围
		if p.Across < math.Min(p1.Across, p2.Across) || p.Across > math.Max(p1.Across, p2.Across) {
			p1 = p2
			// 下一个射线左点
			continue
		}

		// ray is crossing over by the algorithm (common part of) ， 射线交叉算法 (通用部分)
		if p.Across > math.Min(p1.Across, p2.Across) && p.Across < math.Max(p1.Across, p2.Across) {
			// x is before of ray， X在射线之上
			count, ok, _ := pointOnRay(p, p1, p2, precision)
			intersectCount += count
			if ok {
				return ok, nil
			}
		}

		// special case when ray is crossing through the vertex， 当射线穿过顶点时的特殊情况
		count := pointCrossPoints(p, p1, p2, pts, N, i, isOutDoorCoordinates)
		intersectCount += count

		// next ray left point ，  下一条射线的左顶点
		p1 = p2
	}

	// 偶数在多边形外
	if intersectCount%2 == 0 {
		return false, nil
		// 奇数在多边形内
	}
	return true, nil
}

// pointOnRay .
func pointOnRay(point, p1, p2 operationPoint, precision float64) (int, bool, error) {
	// 覆盖在水平线之上时
	var intersectCount = 0
	if point.Vertical <= math.Max(p1.Vertical, p2.Vertical) {
		if p1.Across == p2.Across && point.Vertical >= math.Min(p1.Vertical, p2.Vertical) {
			return 0, true, nil
		}
		// 射线是垂直时
		if p1.Vertical == p2.Vertical {
			// 覆盖在垂直射线上时
			if p1.Vertical == point.Vertical {
				return 0, true, nil
				// before ray
			}
			intersectCount++
			// cross point on the left side
		} else {
			// Y交点
			intersectY := (point.Across-p1.Across)*(p2.Vertical-p1.Vertical)/(p2.Across-p1.Across) + p1.Vertical
			// 覆盖在射线上
			if math.Abs(point.Vertical-intersectY) < precision {
				return 0, true, nil
			}

			// 在射线之上
			if point.Vertical < intersectY {
				intersectCount++
			}
		}
	}
	return intersectCount, false, nil
}

// pointCrossPoints .
func pointCrossPoints(point, p1, p2 operationPoint, pts []Point, N, i int, isOutDoorCoordinates bool) int {
	var intersectCount = 0
	// 当p点穿过P2
	if point.Across == p2.Across && point.Vertical <= p2.Vertical {
		// next vertex， 下一个顶点
		p3 := operationPoint{pts[(i+1)%N].X, pts[(i+1)%N].Y}
		if isOutDoorCoordinates {
			p3 = operationPoint{pts[(i+1)%N].Longitude, pts[(i+1)%N].Latitude}
		}
		// P点的X坐标在p1和p3的X坐标之间时
		if point.Across >= math.Min(p1.Across, p3.Across) && point.Across <= math.Max(p1.Across, p3.Across) {
			intersectCount++
		}
		intersectCount += 2
	}
	return intersectCount
}

// Point .
type Point struct {
	X         float64 `json:"x"`
	Y         float64 `json:"y"`
	Latitude  float64 `json:"latitude"`  //纬度
	Longitude float64 `json:"longitude"` //经度
}
